package com.vitogiovanni.tobook.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;
import javax.inject.Named;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiMethod.HttpMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.datanucleus.query.JDOCursorHelper;
import com.vitogiovanni.tobook.utilities.AnnullamentoPrenotazioneException;
import com.vitogiovanni.tobook.utilities.JsonParser;
import com.vitogiovanni.tobook.utilities.PrenotazioneFallitaException;

@Api(name = "prenotazioneendpoint", namespace = @ApiNamespace(ownerDomain = "vitogiovanni.com", ownerName = "vitogiovanni.com", packagePath = "tobook.entity"))
public class PrenotazioneEndpoint {
	

	/**
	 * This method lists all the entities inserted in datastore.
	 * It uses HTTP GET method and paging support.
	 *
	 * @return A CollectionResponse class containing the list of all entities
	 * persisted and a cursor to the next page.
	 */
	@SuppressWarnings({ "unchecked", "unused" })
	@ApiMethod(name = "listPrenotazione")
	public CollectionResponse<Prenotazione> listPrenotazione(
			@Nullable @Named("cursor") String cursorString,
			@Nullable @Named("limit") Integer limit) {

		PersistenceManager mgr = null;
		Cursor cursor = null;
		List<Prenotazione> execute = null;

		try {
			mgr = getPersistenceManager();
			Query query = mgr.newQuery(Prenotazione.class);
			if (cursorString != null && cursorString != "") {
				cursor = Cursor.fromWebSafeString(cursorString);
				HashMap<String, Object> extensionMap = new HashMap<String, Object>();
				extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
				query.setExtensions(extensionMap);
			}

			if (limit != null) {
				query.setRange(0, limit);
			}

			execute = (List<Prenotazione>) query.execute();
			cursor = JDOCursorHelper.getCursor(execute);
			if (cursor != null)
				cursorString = cursor.toWebSafeString();

			// Tight loop for fetching all entities from datastore and accomodate
			// for lazy fetch.
			for (Prenotazione obj : execute)
				;
		} finally {
			mgr.close();
		}

		return CollectionResponse.<Prenotazione> builder().setItems(execute)
				.setNextPageToken(cursorString).build();
	}

	/**
	 * This method gets the entity having primary key id. It uses HTTP GET method.
	 *
	 * @param id the primary key of the java bean.
	 * @return The entity with primary key id.
	 */
	@ApiMethod(name = "getPrenotazione")
	public Prenotazione getPrenotazione(@Named("id") Long id) {
		PersistenceManager mgr = getPersistenceManager();
		Prenotazione prenotazione = null;
		try {
			prenotazione = mgr.getObjectById(Prenotazione.class, id);
		} finally {
			mgr.close();
		}
		return prenotazione;
	}

	/**
	 * This inserts a new entity into App Engine datastore. If the entity already
	 * exists in the datastore, an exception is thrown.
	 * It uses HTTP POST method.
	 *
	 * @param prenotazione the entity to be inserted.
	 * @return The inserted entity.
	 */
	@ApiMethod(name = "insertPrenotazione")
	public Prenotazione insertPrenotazione(Prenotazione prenotazione) {
		PersistenceManager mgr = getPersistenceManager();
		try {
			if (containsPrenotazione(prenotazione)) {
				throw new EntityExistsException("Object already exists");
			}
			mgr.makePersistent(prenotazione);
		} finally {
			mgr.close();
		}
		return prenotazione;
	}

	/**
	 * This method is used for updating an existing entity. If the entity does not
	 * exist in the datastore, an exception is thrown.
	 * It uses HTTP PUT method.
	 *
	 * @param prenotazione the entity to be updated.
	 * @return The updated entity.
	 */
	@ApiMethod(name = "updatePrenotazione")
	public Prenotazione updatePrenotazione(Prenotazione prenotazione) {
		PersistenceManager mgr = getPersistenceManager();
		try {
			if (!containsPrenotazione(prenotazione)) {
				throw new EntityNotFoundException("Object does not exist");
			}
			mgr.makePersistent(prenotazione);
		} finally {
			mgr.close();
		}
		return prenotazione;
	}

	/**
	 * This method removes the entity with primary key id.
	 * It uses HTTP DELETE method.
	 *
	 * @param id the primary key of the entity to be deleted.
	 */
	@ApiMethod(name = "removePrenotazione")
	public void removePrenotazione(@Named("id") Long id) {
		PersistenceManager mgr = getPersistenceManager();
		try {
			Prenotazione prenotazione = mgr.getObjectById(Prenotazione.class,
					id);
			mgr.deletePersistent(prenotazione);
		} finally {
			mgr.close();
		}
	}

	private boolean containsPrenotazione(Prenotazione prenotazione) {
		PersistenceManager mgr = getPersistenceManager();
		boolean contains = true;
		try {
			mgr.getObjectById(Prenotazione.class, prenotazione.getId());
		} catch (javax.jdo.JDOObjectNotFoundException ex) {
			contains = false;
		} finally {
			mgr.close();
		}
		return contains;
	}

	private static PersistenceManager getPersistenceManager() {
		return PMF.get().getPersistenceManager();
	}

	
	/**
	 * Try to add a "Prenotazione". Return false if the operation goes wrong
	 * @param idLocale
	 * @param nomeCliente
	 * @param emailCliente
	 * @param data
	 * @param durata
	 * @param numeroPosti
	 * @return
	 */
	@ApiMethod(name = "prenota", path="prenota", httpMethod=HttpMethod.POST)
	public Prenotazione prenota (
			@Named("idLocString")String idLocaleJsonKey, 
			@Named("nominativo")String nomeCliente, 
			@Named("idUtente")String emailCliente, 
			@Named("orario")long dataLong, 
			@Named("durata")long durata, 
			@Named("numeroPosti")int numeroPosti,
			@Named("notePrenotazione")String notePrenotazione) throws PrenotazioneFallitaException
	{
		PersistenceManager mgr = getPersistenceManager();
		Locale locale = null;
		Date data = new Date(dataLong);
		Key idLocale = JsonParser.jsonToKey(idLocaleJsonKey);
		Set<Prenotazione> prenotazioni = null;
		Set<OrariApertura> orariApertura = null;
		int postiDisponibili = 0;
		if(data == null)
		{
			String message = "Data non valida";
			throw new PrenotazioneFallitaException(message);
		}
		if(numeroPosti <= 0)
		{
			String message = "Inserire numero posti da prenotare";
			throw new PrenotazioneFallitaException(message);
		}
		if(nomeCliente == null || nomeCliente.length()<3)
		{
			String message = "Nome prenotazione obbligatorio";
			throw new PrenotazioneFallitaException(message);
		}
		try {
			locale = mgr.getObjectById(Locale.class, idLocale);
			if(locale != null)
			{
				prenotazioni = (Set<Prenotazione>) locale.getPrenotazioni();
				for(Prenotazione pren : prenotazioni)
				{
					pren.getOrario();
				}
				orariApertura = (Set<OrariApertura>) locale.getOrariLocale();
				for(OrariApertura ora: orariApertura)
				{
					ora.getOrarioApertura();
					ora.getOrarioChiusura();
				}
				
			}
		} finally {
			mgr.close();
		}
		if(locale == null)
		{
			String message = "Locale non trovato";
			throw new PrenotazioneFallitaException(message);
		}
		if(data == null)
		{
			String message = "Data non valida";
			throw new PrenotazioneFallitaException(message);
		}
		if(numeroPosti <= 0)
		{
			String message = "Inserire numero posti da prenotare";
			throw new PrenotazioneFallitaException(message);
		}
		postiDisponibili = locale.getNumeroPosti();
		long dataStart = data.getTime();
		long dataEnd = data.getTime() + durata;
		if(data.before(new Date()))
		{
			String message = "Impossibile prenotare per una data nel passato";
			throw new PrenotazioneFallitaException(message);
		}
		// Prenotazione valida "in assoluto" per gli orari del locale?
		long oraInizio = dataStart % (1000*60*60*24);
		long oraFine = oraInizio + durata;
		boolean orarioValido = false;
		for(OrariApertura orario: orariApertura)
		{
			if(oraInizio >= orario.getOrarioApertura().getTime() && oraFine <= orario.getOrarioChiusura().getTime())
			{
				orarioValido = true;
				break;
			}
		}
		if(!orarioValido)
		{
			String message = "Locale chiuso nell'intervallo di tempo selezionato";
			throw new PrenotazioneFallitaException(message);
		}
		// Posti disponibili per quel giorno?		

		for(Prenotazione pren: prenotazioni)
		{
			long prenStart = pren.getOrario().getTime();
			long prenEnd = prenStart + pren.getDurata();
			if(!(dataStart >= prenEnd || prenStart >= dataEnd ))
			{
				postiDisponibili = postiDisponibili - pren.getNumeroPosti();
			}
		}
		if(postiDisponibili < numeroPosti)
		{
			String message = "Non ci sono abbastanza posti disponibili nell'intervallo di tempo selezionato";
			throw new PrenotazioneFallitaException(message);
		}
		
		// Creo Prenotazione
		
		Prenotazione prenotazione = null;
		mgr = getPersistenceManager();
		try {
			locale = mgr.getObjectById(Locale.class, idLocale);
			if(locale != null)
			{
				prenotazioni = (Set<Prenotazione>) locale.getPrenotazioni();
				prenotazione = new Prenotazione();
				prenotazione.setDurata(durata);
				prenotazione.setIdUtente(emailCliente);
				prenotazione.setNominativo(nomeCliente);
				prenotazione.setNumeroPosti(numeroPosti);
				prenotazione.setOrario(data);
				prenotazione.setNotePrenotazione(notePrenotazione);
				if(prenotazioni == null)
				{
					prenotazioni = new HashSet<Prenotazione>();
				}
				prenotazioni.add(prenotazione);
				//locale.setPrenotazioni(prenotazioni);
				mgr.makePersistent(locale);
				UtenteEndpoint utenteendpoint = new UtenteEndpoint();
				utenteendpoint.addPrenotazione(prenotazione.getId(), emailCliente);
			}
		}
			catch(Exception e)
			{
				throw new PrenotazioneFallitaException(e.getMessage());
			}
		finally {
			mgr.close();
		}

		
		if(prenotazione!= null)
		{
			return prenotazione;
		}
		else
		{
			String message = "";
			throw new PrenotazioneFallitaException(message);
		}
	
	}
	
	/**
	 * This method lists all the entities inserted in datastore with idUtente = number.
	 * It uses HTTP GET method and paging support.
	 *
	 * @return A CollectionResponse class containing the list of all entities
	 * persisted with idUtente = number and a cursor to the next page.
	 */
	@SuppressWarnings({ "unchecked"})
	@ApiMethod(name = "listPrenotazioneByEmail" , path="list_prenotazione_by_email", httpMethod=HttpMethod.GET)
	public List<Prenotazione> listPrenotazioneByEmail(@Named("emailUtente")String emailUtente) {

		List<Prenotazione> listaPrenotazioni = new ArrayList<Prenotazione>();

		UtenteEndpoint utenteEndpoint = new UtenteEndpoint();
		Utente utente = utenteEndpoint.findByEmail(emailUtente);
		if(utente == null)
		{
			utenteEndpoint.insertUtente(new Utente(emailUtente));
			return listaPrenotazioni;
		}
		Set<Key> listaKeyPrenotazioni = utente.getPrenotazioni();
		PersistenceManager mgr = getPersistenceManager();
		try {
			for (Key keyIndex : listaKeyPrenotazioni) {
				Prenotazione prenotazione = null;
				prenotazione = mgr.getObjectById(Prenotazione.class, keyIndex);
				if(prenotazione!=null)
				{
					prenotazione.getLocalePrenotato();
					prenotazione.getLocalePrenotato().getCitta();
					prenotazione.getLocalePrenotato().getNome();
					prenotazione.getLocalePrenotato().getVia();
					prenotazione.getId();
					prenotazione.getOrario();
					listaPrenotazioni.add(prenotazione);
				}
			}
		} finally {
			mgr.close();
		}
		return listaPrenotazioni;
	}
	
	
	/**
	 * This method removes the entity with primary key obtained from idPrenotazione if the idUtente match
	 * It uses HTTP DELETE method.
	 * @param idPrenotazione
	 * @param idUtente
	 * @throws AnnullamentoPrenotazioneException 
	 */
	@ApiMethod(name = "annullaPrenotazione", path="annulla_prenotazione")
	public void annullaPrenotazione(
			@Named("idPrenotazione") String idPrenotazione,
			@Named("idUtente") String idUtente) throws AnnullamentoPrenotazioneException {
		
		Key keyPrenotazione = JsonParser.jsonToKey(idPrenotazione);
		
		PersistenceManager mgr = getPersistenceManager();
		try {
			Prenotazione prenotazione = mgr.getObjectById(Prenotazione.class,
					keyPrenotazione);
			Locale locale = prenotazione.getLocalePrenotato();
			if(prenotazione.getIdUtente().compareTo(idUtente)==0
					&& prenotazione.getOrario().compareTo(new Date()) >= 1)
			{
				// POSSIBILI CONDIZIONI DI ANNULLAMENTO PRENOTAZIONE DA
				// DEFINIRE
				if(locale.getPrenotazioni().contains(prenotazione))
				{
					locale.getPrenotazioni().remove(prenotazione);
				}
				Utente utente = new UtenteEndpoint().findByEmail(idUtente);
				Set<Key> prenotazioniUt = utente.getPrenotazioni();
				if( prenotazioniUt.contains(keyPrenotazione))
				{
					 prenotazioniUt.remove(keyPrenotazione);
				}
				utente.setPrenotazioni(prenotazioniUt);
				new UtenteEndpoint().updateUtente(utente);
				//utenteendpoint.removePrenotazione(prenotazione); DA IMPLEMENTARE
			}
			else
			{
				throw new AnnullamentoPrenotazioneException("Impossibile annullare la prenotazione");
			}
		} finally {
			mgr.close();
		}
	}
	
	
	
}

